using System;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Globalization;
using System.ComponentModel.Design;
using Microsoft.Win32;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
using System.Windows.Forms;
using Microsoft.VisualStudio.Text.Tagging;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Formatting;
using Microsoft.VisualStudio.Utilities;
using Microsoft.VisualStudio.Editor;
using System.Collections.Generic;
using Microsoft.VisualStudio.Text.Operations;
using Microsoft.VisualStudio.VCCodeModel;
using System.Text;
using System.Drawing;
using EnvDTE;
using EnvDTE80;
using System.IO;
using System.Reflection;



namespace FunctionBar
{

    // my original plan was to use i0 and i1 to identify a
    // substring that should be highlighted any time the function
    // is selected, as that would match the behavior of XCode's
    // bar.  however, the current version of the bar simply places
    // the cursor at the front of a function definition, thus
    // the value of i1 is never used.  
    //
    // and this is a good thing, because the pattern matching code
    // doesn't always calculate i1.
    public class FunctionMatch
    {
        public string name="";
        public int i0 = 0, i1 = 0;
        public List<string> scope = new List<string>();
        public MatchType type;

        public FunctionMatch(int i0, int i1, string name)
        {
            this.i0 = i0; this.i1 = i1;
            this.name = name;
            type = MatchType.FUNCTION;
        }
    }


public class CBar : FunBar
{



    List<FunctionMatch> flist = new List<FunctionMatch>();

    Regex c2 = new Regex(@"(/\*)[^(\*/)]*\G", RegexOptions.RightToLeft);
    Regex c3 = new Regex(@"(\n|\r|^)[ \t]*//[^\n\r]*\G", RegexOptions.RightToLeft);
    Regex macro = new Regex(@"(\n|\r|^)[ \t]*\#[ \t]*define[^\n\r]*\G", RegexOptions.RightToLeft);

    //ImageList images = new ImageList();



    public override uint nentries()
    {
        return (uint)flist.Count;
    }


    public override int GetEntryAttributes(int iCombo, int iIndex, out uint pAttr)
    {
        if (flist[iIndex].type == MatchType.COMMENT)
        {
            pAttr = (uint)DROPDOWNFONTATTR.FONTATTR_BOLD;
            return 0;
        }
        pAttr = 0;
        return 0;
    }

    public override int GetEntryImage(int iCombo, int iIndex, out int piImageIndex)
    {
        piImageIndex = 0;
        if (flist[iIndex].scope.Count == 0) piImageIndex = 1;
        if (flist[iIndex].type == MatchType.COMMENT)
        {
            piImageIndex = 2;
            // don't add an image in the case that the comment line is
            // blank.
            if (flist[iIndex].name == "") piImageIndex = 4;
        }
        if (flist[iIndex].type == MatchType.CONSTRUCTOR)
        {
            piImageIndex = 6;
        }
        if (flist[iIndex].type == MatchType.DESTRUCTOR)
        {
            piImageIndex = 6;
        }
        if (flist[iIndex].type == MatchType.AUTO)
        {
            piImageIndex = 5;
        }
        if (flist[iIndex].type == MatchType.TABLE)
        {
            piImageIndex = 3;
        }
        return 0;
    }

    public override int GetEntryText(int iCombo, int iIndex, out string ppszText)
    {

        // never add scopes to comment entries.
        if (flist[iIndex].type == MatchType.COMMENT)
        {
            ppszText = flist[iIndex].name;
            return 0;
        }

        string scopes="";
        foreach(string s in flist[iIndex].scope) {
            scopes = s + " :: "+ scopes;
        }
        if (scopes.Length > 0)
            ppszText = scopes + flist[iIndex].name;
        else 
            ppszText = flist[iIndex].name;
        return 0;
    }


    void delete_comments()
    {

        for (int i = 0; i < file_data.Length - 1; i++)
        {
            //skip forward when we hit inline comments.
            if (file_data[i] == '/' && file_data[i + 1] == '/')
            {
                while (i < file_data.Length && file_data[i] != '\n') i++;
                continue;
            }

            if (file_data[i] == '/' && file_data[i + 1] == '*')
            {
                int j;
                for (j = i + 2; j < file_data.Length - 1; j++)
                {
                    if (file_data[j] == '*' && file_data[j + 1] == '/') break;
                }
                // i and j are the bounds of a comment, so,
                // remove entries with positions between i and j;
                for (int k = 0; k < flist.Count; )
                {
                    if (flist[k].i0 < j && flist[k].i0 > i)
                    {
                        flist.RemoveAt(k);
                    }
                    else k++;
                }
            }
        }
    }

    int closing_paren(int i)
    {
        int depth = 0;
        for (; i < file_data.Length; i++)
        {
            if (file_data[i] == '{') depth++;
            if (file_data[i] == '}')
            {
                if (depth == 0)
                    return i;
                else depth--;
            }
        }
        return i;
    }

    //
    // so, the visual studio c++ dropdown bar sucks.  it's easily
    // confused by namespaces and macros.  and when you dig into
    // the SDK, it quickly becomes clear why it sucks -- the
    // various CodeElements that VisualStudio maintains for each
    // file are often wrong.
    //
    // at a high level, accurately calculating where in a file each
    // function declaration starts is a hard enough problem that
    // expecting an IDE to do it perfectly is unreasonable.  for
    // example, if the programmer is working on something like:
    // http://www0.us.ioccc.org/1987/wall.c  
    // the only way you can hope to get a correct list of the
    // functions in the file is by running a full C preprocessor
    // pass, followed by a parser pass.
    //
    // invoking a parser pass actually isn't that unreasonable an
    // option -- given that maintaining the function list is
    // something we can easily do in a background process.
    //
    // the real problem with using a parser is that we'd like our
    // function summery list to be more or less correct even while
    // the programmer is actively editing their file, and thus, we 
    // want to generate sensible values even when there are syntax
    // errors.
    //
    // but if we introduce a few syntax errors into something like
    // wall.c, trying to guess where function definitions occur 
    // becomes virtually impossible.
    //
    // however, getting a function list that will correctly reflect 
    // the programmer's intentions for most C/C++ sources is not an
    // unreasonable task, even allowing for some syntax errors in
    // the file. all we need is to define character patterns that
    // are likely to be function declarations, and then add all
    // instances of those patterns to our function list.
    //
    // my pattern matching logic is fairly complex, largely because
    // i've made an effort to avoid some of the sillier mistakes
    // made by XCode's code navigator.  for example, the string
    // "foo(5) {" is perfectly legal in C++, (provided it  occurs
    // at the end of a constructor's initializer list), and it
    // should not be mistaken for the start of a function
    // definition.
    //
    // here's some other tricky cases i've taken pains to not
    // mistake for function definitions:
    //
    //  int foo :: foo = {bar} 
    //  orz ? foo::foo() : bar(5);  
    //  else BOOST_FOREACH(int i,v) {}
    //
    // to write something that satisfies my own standards of
    // robustness requires fairly complicated pattern matching
    // logic.  in an attempt to keep the logic simple, i make
    // multiple passes through the file, though doing so
    // likely makes the algorithm a bit less efficient than it
    // would be otherwise.
    //
    // my own standards aren't really that high, however.  for
    // example, here's a case that my own logic will get confused
    // by:
    // 
    // struct bar { struct foo { int bar() {} }; };
    //
    // (the result here is that bar will get 2 entries in the
    // function list, once as a suspected constructor, and once as
    // a suspected inlined member function.)
    //
    // altering the code to fix this case is certainly possible --
    // but, my sense is that anyone who uses this type of construct 
    // is a weird enough programmer that they won't be surprised to
    // discover some slight weirdness in their function lists.
    //
    void get_function_positions()
    {

        //
        // crude regex for getting function definitions.
        // won't handle constructors, or pay attention to comments.
        // this regex insists that the candidate function have
        // something that looks like a specified return type -- thus,
        // it shouldn't match iteration constructs, constructors, or
        // deconstructors.
        //
        Regex r = new Regex(@"(\w+\s+)[&\s\*]*[ \t]*((?<scope>\w+)\s*::\s*)*(?<name>\w+)\s*\([^;}{]*\)\s*(const\s*)?\{");

        Match m = r.Match(file_data);

        //string groups = m.Groups.ToString();
        while (m.Success)
        {

            string rtype = m.Groups[1].Value;





            string name = m.Groups["name"].Value;
            int i = m.Index;

            //make sure the function definition isn't inside an inline comment.
            //
            // edit -- i've expanded these cases for function definitions,
            // to get around a problem i'd been having with functions being
            // detected inside comments.  basically, in the case that the
            // function definition appears to start inside a comment, but
            // appears to end outside a comment, we need to restart the
            // search after the end of the functions name.
            //
            // i suppose in theory, similar rules should apply for things
            // like struct scoping and so on, but, i've yet to see that
            // issue come up in practice.
            Match m3 = c3.Match(file_data, i);
            if (m3.Success)
            {
                int ni = m.Groups["name"].Index + m.Groups["name"].Length;
                m = r.Match(file_data, ni);
                continue;
            }
            m3 = c2.Match(file_data, i);
            if (m3.Success)
            {
                int ni = m.Groups["name"].Index + m.Groups["name"].Length;
                m = r.Match(file_data, ni);
                continue;
            }
            m3 = macro.Match(file_data, i);
            if (m3.Success)
            {
                int ni = m.Groups["name"].Index + m.Groups["name"].Length;
                m = r.Match(file_data, ni);
                continue;
            }

            int i1 = m.Index + m.Length - 1;
            Group g = m.Groups["scope"];
            FunctionMatch fun = new FunctionMatch(i, i1, name);
            if (g.Value != "")
            {

                foreach (Capture capture in g.Captures)
                {
                    fun.scope.Insert(0,capture.Value);
                }

            }

            string match = m.Value;
            Match old_match = m;
            m = m.NextMatch();



            // avoid matching:
            // else if() {
            if (rtype.StartsWith("else")) continue;
            if (rtype.StartsWith("define"))
            {
                Regex hash = new Regex(@"\#[ \t]*\G", RegexOptions.RightToLeft);
                Match hm = hash.Match(file_data, i);
                if (hm.Success) continue;
            }

            // these keywords can be hard to distinguish from function
            // calls.  particularly if they're used by the preprocessor. 
            // for example, the following is not a function:
            //  # if DEBUG if(true) {
            if (name == "if") continue;
            if (name == "else") continue;

           

            flist.Add(fun);

            //string last = file_data.Substring(i1);

        }
    }

    //
    // matching constructors and deconstructors gets particularly 
    // tricky, because a substring like "foo::foo() : bar(5)" is
    // potentially either a constructor definition or part of a
    // ternary op.
    //
    // moreover, something like foreach(...) { is potentially
    // either an inlined constructor definition, or an iteration
    // macro.
    // 
    // we can deal with the foreach(...) case by only looking for
    // inline constructor definitions when we know we're inside a
    // structure definition, and thus have the luxury of creating a
    // pattern that searches for functions having the same name as 
    // the structure.
    //
    // that just leaves the non-inlined foo::foo() cases -- and for
    // these cases, we can write a regex that should distinguish
    // them from ternary ops.
    //
    void add_constructors()
    {
        // things to worry about:
        //  foo :: foo = {10}
        //  foo :: foo() = {10}

        Regex r = new Regex(@"(\w+)\s*::(~?\w+)[^;{]+\)\s*(:[^;{=])?{");
        Match m = r.Match(file_data);

        while (m.Success)
        {
            //  foo::while(i) {}

            string scope = m.Groups[1].Value;
            string fun = m.Groups[2].Value;

            

            string name = m.Groups[1].Value + " :: " + m.Groups[2].Value;

            int i = m.Index;
            m = m.NextMatch();

            //make sure the definition isn't inside an inline comment.
            Match m3 = c3.Match(file_data, i);
            if (m3.Success) continue;
            m3 = macro.Match(file_data, i);
            if (m3.Success) continue;

            // only add stuff that looks like a constructor or destructor
            if (scope != fun && "~" + scope != fun) continue;

            FunctionMatch fm = new FunctionMatch(i, i, fun);
            if(scope==fun) fm.type = MatchType.CONSTRUCTOR;
            else fm.type = MatchType.DESTRUCTOR;
            fm.scope.Add(scope);
            insert_function(fm);

        }
    }


    //
    // template specializations are also tricky, because we can
    // have all sorts of weird characters inside the function name
    // -- for example:
    //
    // template<>int * bar<foo<A>,int &>(int a=5) {
    //
    // thus, i handle them as a special case, rather than trying to
    // generalize the main function matching code to detect
    // template specializations.
    //
    void template_specializations()
    {
        Regex r = new Regex(@"template\s*<\s*>\s*([^;{}]+)\s+(\w+)\s*<([^;{}\(\)]+)>\s*\([^;{}]+{");

        Match m = r.Match(file_data);
        while (m.Success)
        {
            string fname = m.Groups[2].Value;
            int i = m.Groups[1].Index;
            m = m.NextMatch();

            //make sure the definition isn't inside an inline comment or macro.
            Match m3 = c3.Match(file_data, i);
            if (m3.Success) continue;
            m3 = macro.Match(file_data, i);
            if (m3.Success) continue;

            insert_function(new FunctionMatch(i,i, fname));
        }
    }

    // add autos if it looks like they're C++ lambda expressions.
    public void add_autos()
    {
        Regex r = new Regex(@"auto\s+(\w+)\s*=\s*\[");
        Match m = r.Match(file_data);

        while (m.Success)
        {

            string fname = m.Groups[1].Value;

            int i = m.Groups[1].Index;
            m = m.NextMatch();

            FunctionMatch fun = new FunctionMatch(i, i, fname);
            fun.type = MatchType.AUTO;
            insert_function(fun);
        }
    }

    void add_classes()
    {
       // experimenting with adding classes to the list.
        bool add_classes = true;
        if (!add_classes) return;

        Regex r = new Regex(@"((class)|(struct))\s+(?<name>\w+)\s*[:{<]");
        Match m = r.Match(file_data);

        while (m.Success)
        {

            string name = m.Groups["name"].Value;
            int i = m.Index;
            m = m.NextMatch();

            FunctionMatch fun = new FunctionMatch(i, i, name);
            fun.type = MatchType.TABLE;
            insert_function(fun);
        }
    }

    // add !! comments
    public void add_notes()
    {
        Regex r = new Regex(@"(\n|\r)([ \t]*)///*((!!)|([ \t]*MARK:[ \t]*))(?<text>[^\n\r]*)");
        Match m = r.Match(file_data);

        while (m.Success)
        {

            string text = m.Groups["text"].Value;

            text = text.Replace("\t", "  ");

            int i = m.Groups[2].Index;
            m = m.NextMatch();

            FunctionMatch fun = new FunctionMatch(i, i, text);
            fun.type = MatchType.COMMENT;
            insert_function(fun);
        }

    }

    List<List<string>> scopes = new List<List<string>>();
    int insert_function(FunctionMatch fun, bool newscope=false)
    {
         int k;
         for (k = 0; k < flist.Count; k++)
         {
             if (flist[k].i0 > fun.i0)
             {
                 flist.Insert(k, fun);
                 if (newscope) scopes.Add(new List<string>());
                 return k;
             }
         }

         flist.Add(fun);
         if (newscope) scopes.Add(new List<string>());
         return k;
    }



    void struct_scopes()
    {
        
        scopes.Clear();
        int k;
        for (k = 0; k < flist.Count; k++)
        {
            scopes.Add(new List<string>());
        }

        Regex r = new Regex(@"((class)|(struct))\s+(?<name>\w+)");
        Match m = r.Match(file_data);



        while (m.Success)
        {
            string name = m.Groups["name"].Value;
            int i = m.Index;
            m = m.NextMatch();

            //make sure the definition isn't inside an inline comment.
            Match m3 = c3.Match(file_data, i);
            if (m3.Success) continue;
            m3 = macro.Match(file_data, i);
            if (m3.Success) continue;


            bool prototype = true;
            int gcount = 0;
            for (; i < file_data.Length; i++)
            {
                if (file_data[i] == '<') gcount++;
                if (file_data[i] == '>') gcount--;
                if (file_data[i] == ';') break;
                if (file_data[i] == '{')
                {
                    prototype = false;
                    break;
                }
            }

            // we're probably inside a template definition
            if (gcount != 0) continue;

            if (!prototype)
            {
                int j = closing_paren(i + 1);


                //now we know we have a struct scope -- lets look for inlined constructors/destructors.
                add_inline_constructors(name, i, j);

                for (k = 0; k < flist.Count; k++)
                {
                    if (flist[k].i0 < j && flist[k].i0 > i)
                    {
                        scopes[k].Add(name);
                    }
                }

            }

        }

        for (k = 0; k < flist.Count; k++)
        {
            int i = flist[k].scope.Count;
            foreach(string s in scopes[k]) {
                flist[k].scope.Insert(i,s);
            }

        }

    }

    //
    // in the case of inlined constructors/destructors, we know
    // that the function names we're looking for must be either
    // ~scope or scope, so, we don't need to worry so much about
    // miss-identifying iteration constructs as functions.
    //
    void add_inline_constructors(string scope, int i, int j)
    {
        Regex r = new Regex(@"(~?" + scope + @")\s*\([^;{=]+{");
        Match m = r.Match(file_data, i, j - i);
        while (m.Success)
        {
            string name = m.Groups[1].Value;
            int ii = m.Index;
            m = m.NextMatch();

            //make sure the definition isn't inside an inline comment.
            Match m3 = c3.Match(file_data, i);
            if (m3.Success) continue;
            m3 = macro.Match(file_data, i);
            if (m3.Success) continue;

            FunctionMatch fun = new FunctionMatch(ii, ii, name);
            if( name[0] != '~') {
                fun.type = MatchType.CONSTRUCTOR;
            }

            insert_function(fun,true);
        }
    }


    public override void refresh_functions()
    {
        flist.Clear();        

        get_function_positions();

        template_specializations();

        add_constructors();

        // do a pass through the file where we account for struct/class
        // scoping.

        struct_scopes();

        add_autos();
        add_classes();


        // do another pass through the file, and remove anything that
        // looks like it's inside a /* */ comment.

        if (flist.Count > 0)
        {
            delete_comments();
        }

        // do this after deleting commented functions -- as notes can be commented.
        add_notes();


        bar.RefreshCombo(0, 0);

        calc_caret_pos();
    }


    public override void calc_caret_pos()
    {
        int r, c, caret, vspace;

        vsTextView.GetCaretPos(out r, out c);
        vsTextView.GetNearestPosition(r, c, out caret, out vspace);

        last_caret_pos = caret;

        int i;
        for (i = 0; i < flist.Count; i++)
        {
            if (flist[i].i0 > caret)
            {
                break;
            }
        }
        i--;

        if (i >= 0) bar.SetCurrentSelection(0, i);
    }

    public override int OnItemChosen(int iCombo, int iIndex)
    {

        if (null == vsTextView) return 0;

        int r, c;
        vsTextView.GetLineAndColumn(flist[iIndex].i0, out r, out c);

        vsTextView.CenterLines(r, 1);
        vsTextView.CenterColumns(r, c, 1);
        vsTextView.SetCaretPos(r, c);

        vsTextView.SendExplicitFocus();

        return 0;
    }


    //
    // old code for trying to generate elements via the
    // CodeElements or IClassifiers.
    // both of which proved error-prone enough that i decided to
    // implement my own pattern matching logic.

    /*
  _DTE dte = ServiceProvider.GetService(typeof(SDTE)) as _DTE;

  Document doc = dte.ActiveDocument;
  VCFileCodeModel code = doc.ProjectItem.FileCodeModel as VCFileCodeModel;



  if(code==null) return 0;
          

  add_functions(code.Functions);

           

  bar.RefreshCombo(0, 0);
            
  return 0;
  */

    /*
    ITextBuffer wbuffer = EditorAdaptersFactoryService.GetDataBuffer(lines);


    IClassifier classifier = AggregatorFactory.GetClassifier(wbuffer);

    IWpfTextView wview = EditorAdaptersFactoryService.GetWpfTextView(vsTextView);


    ITextSnapshot shot = wview.TextSnapshot;
    Span span0 = new Span(0, shot.Length);
    NormalizedSpanCollection nspan = new NormalizedSpanCollection(span0);
    NormalizedSnapshotSpanCollection spans = new NormalizedSnapshotSpanCollection(shot, nspan);


    int nspans = spans.Count;

    foreach (SnapshotSpan span in spans)
    {
        //string spanstring=span.ToString();

        int i = 0;
        int p0 = -1;
        int pdepth=0;

        IList<ClassificationSpan> v = classifier.GetClassificationSpans(span);

        string OP="operator";

        // Look at each classification span inside the requested span
        foreach (ClassificationSpan classification in v)
        {
            if (classification.ClassificationType.IsOfType(OP)  )
            {
                string op = classification.Span.GetText();
                    
                if(op == ";" || op == "};" || op == "}") {
                    pdepth=0;
                }
                else if(op==")") {
                    pdepth--;
                    if(pdepth<0) {
                        pdepth=0;
                        continue;
                    }

                    if(pdepth==0 && v.Count>i+1) {
                            
                        if(v[i+1].ClassificationType.IsOfType(OP)  && v[i+1].Span.GetText() == "{" ) {
                            ClassificationSpan maybe_fun = v[p0-1];
                            functions.Add(maybe_fun.Span.GetText());
                        }
                    }

                            

                }
                else if(op=="(") {
                    if(pdepth==0 && i>0) p0=i;
                    pdepth++;
                }
                else if(op=="()") {
                    if(pdepth==0) {
                        ClassificationSpan maybe_fun = v[i-1];
                        functions.Add(maybe_fun.Span.GetText());
                    }
                }
            }


            i++;
              
        }
            
        bar.RefreshCombo(0, 0);
    */
    /*
string type = classification.ClassificationType.Classification.ToLower();
string content = classification.Span.GetText();

string foo = "hrm";
*/


    //OutliningRegionTag

    //IList<ClassificationSpan> spans = classifier.GetClassificationSpans(span);





}

}